<?php


	namespace Midi\Parsing;
	
	use Midi\Event;
	use Midi\TrackHeader;
	use Midi\Util\Util;

	class TrackParser extends Parser {
		
		private $eventParser;
		
		private $deltaParser;
		
		private $expectedTrackLength;
		
		private $parsedTrackLength;
		
		public function __construct(EventParser $eventParser = null, DeltaParser $deltaParser = null) {
			$this->eventParser         = ($eventParser === null) ? new EventParser() : $eventParser;
			$this->deltaParser         = ($deltaParser === null) ? new DeltaParser() : $deltaParser;
			$this->expectedTrackLength = 0;
			$this->parsedTrackLength   = 0;
			
			$this->setState(ParseState::TRACK_HEADER);
		}
		
		public function getExpectedTrackLength() {
			return $this->expectedTrackLength;
		}
		
		public function getParsedTrackLength() {
			return $this->parsedTrackLength;
		}
		
		protected function getRawTrackHeader() {
			return $this->read(TrackHeader::LENGTH, true);
		}
		
		public function parseTrackHeader($header) {
			if (strlen($header) !== TrackHeader::LENGTH) {
				throw new \InvalidArgumentException('Track header must be ' . \Midi\TrackHeader::LENGTH . ' bytes');
			}
			
			$id   = Util::unpack(substr($header, 0, 4));
			$size = array_reverse(Util::unpack(substr($header, 4)));
			
			if ($id !== array(0x4D, 0x54, 0x72, 0x6B)) {
				throw new ParseException('Invalid track header, expected [4D 54 72 6B]');
			}
			
			$shift = 0;
			$trackSize = 0;
			foreach ($size as $byte) {
				$trackSize |= ($byte << $shift);
				$shift += 8;
			}
			
			return new TrackHeader($trackSize);
		}
		
		protected function afterSetFile() {
			$this->eventParser->setFile($this->file);
			$this->deltaParser->setFile($this->file);
		}
		
		public function parse() {
			$state = $this->getState();
			$chunk = null;
			switch ($state) {
				case ParseState::TRACK_HEADER:
					$chunk                     = $this->parseTrackHeader($this->getRawTrackHeader());
					$this->parsedTrackLength   = 0;
					$this->expectedTrackLength = $chunk->getSize();
					$this->setState(ParseState::DELTA);
					break;
				case ParseState::DELTA:
					$chunk = $this->deltaParser->parse();
					$this->setState(ParseState::EVENT);
					$this->checkTrackLength($chunk->getLength());
					break;
				case ParseState::EVENT:
					$chunk = $this->eventParser->parse();
					$this->checkTrackLength($chunk->getLength());
					if ($this->getParsedTrackLength() === $this->getExpectedTrackLength()) {
						if (!($chunk instanceof Event\EndOfTrackEvent)) {
							throw new ParseException('Expected end of track');
						} else {
							$this->setState(ParseState::TRACK_HEADER);
						}
					} else {
						$this->setState(ParseState::DELTA);
					}
					break;
				default:
					throw new StateException('Invalid state: ' . $state);
			}
			
			return $chunk;
		}
		
		protected function checkTrackLength($length) {
			$this->parsedTrackLength += $length;
			$expectedLength = $this->getExpectedTrackLength();
			$parsedLength = $this->getParsedTrackLength();
			
			if ($parsedLength > $expectedLength) {
				throw new ParseException('Track data exceeds expected length (' . $parsedLength . '/' . $expectedLength . ')');
			}
		}
		
	}

?>